/* rede.c
 * Luiz Guilherme M. Massa, 1005312
 * Matheus Martins Alves, 10108835
 * Marcelo C. R. Lopes Junior, 10104156
 * Vitor Sousa Negro, 1068104
 */

void* recebePacote(void);
void* enviaPacote(void);
void* enviaPacote2(void);
void* enviaTabelaParaVizinhos(void);
void recebeInfoEnlace(void);
void printTabelaRede(void);
void preencheBufferEnlace();
void* enviaTabelaParaVizinhos(void);

pacote buffer_junta[30];
int controle_junta = 0;
int flag_junta;
segmento data_juncao;


int getChecksumSegmento(segmento seg)
{
    int soma = 0;
    char buffer[sizeof(seg)];
    memcpy(&buffer, &seg, sizeof(segmento));
    soma += getHashChar(buffer, sizeof(buffer));
    return soma;
}


int tabelaRede[NR_MAX_NOS][4];
int tabelaRedeRecebida[NR_MAX_NOS][4];
// [0] = nó destino
// [1] = nó para quem devo enviar
// [2] = custo
// [3] = is_vizinho



void juntaFragmentos(pacote data[], int size)
{
    int join;
    int check_sum;

    for( join = 0 ; join < size ; join++)
        {       
        if(data[0].identificacaoPacote == data[join].identificacaoPacote){
                
                memcpy(((void *) &data_juncao)+data[join].offset,&data[join].segmento, data[join].tamanho_data);
                
                check_sum = getChecksumSegmento(data_juncao);
                if(TESTE_SINC) printf("Checksum LOCAL : %d\n",check_sum);
                if(TESTE_SINC) printf("Checksum PACOTE : %d\n",data[0].checksum_segmento);
                if(data[0].checksum_segmento == check_sum )
                {
                    if(TESTE_SINC) printf("CHECKSUM MATCH\n");         
                    flag_junta = 1;
                }

               else
                 if(TESTE_SINC) printf("NAO JUNTOU\n");  
         }

       
    }    

}

int enviaPacoteInicializado = 0;
int sinc_rede = 0;
pthread_t thread_rede_id[NR_MAX_THREADS];

 void iniciaRede(void) {

    if(TESTE_SINC) printf("Camada de rede iniciada.\n");

    bufferTransporteRede.preenchido = 0;
    bufferRedeTransporte.preenchido = 0;

    recebeInfoEnlace();

    printTabelaRede();

    if(pthread_create(&(thread_rede_id[0]), NULL, &enviaPacote2) != 0) {
        perror("pthread_create()");
        exit(1);
    }

    if(pthread_create(&(thread_rede_id[1]), NULL, &enviaPacote) != 0) {
        perror("pthread_create()");
        exit(1);
    }

    if(pthread_create(&(thread_rede_id[2]), NULL, &recebePacote) != 0) {
        perror("pthread_create()");
        exit(1);
    }
}

void fragmentacaoMTU(pacote pac, int MTU) {
    
    int tamanho_data = sizeof(pac.segmento);
    int i = 0;
    int p = 0;
    int id;
    int padrao_divisao = MTU - (sizeof(quadro) - sizeof(pacote));
    int checksum_segmento;
    if(TESTE_SINC) printf("padrao_divisao:%d\n",padrao_divisao);
    pacote fragmento;
    checksum_segmento = getChecksumSegmento(pac.segmento);
    id = rand() % 1000;
    if(TESTE_SINC) printf("Rede -> Tamanho_data: %d\n",tamanho_data);
    if(TESTE_SINC) printf("Rede -> Checksum Fragmento: %d\n",checksum_segmento);
    while(tamanho_data > padrao_divisao)
    {
        memcpy(&fragmento.segmento,((void *) &pac.segmento)+p,padrao_divisao);
        fragmento.offset = p;
        erro_mtu.MTU = -1;
        p = p + padrao_divisao;
        fragmento.tipo = 'F';
        fragmento.flagFragmentos = 1;
        fragmento.tamanho_data = padrao_divisao;
        fragmento.identificacaoPacote = id;
        fragmento.no_dst = pac.no_src;
        fragmento.no_dst = pac.no_dst;
        fragmento.checksum_segmento = checksum_segmento;
        bufferRedeEnlace2.tamanho_data = padrao_divisao;
        bufferRedeEnlace2.no_dst =tabelaRede[pac.no_dst][1];
        bufferRedeEnlace2.preenchido = 1;
        if(TESTE_SINC) printf("Rede -> Fragmento %d foi para camada de enlace\n",fragmento.offset);
        memcpy(&bufferRedeEnlace2.pacote,&fragmento,sizeof(fragmento));
        sinc_rede = 1;
        while(sinc_rede == 1) usleep(10); 
        tamanho_data = tamanho_data - padrao_divisao;
    }
 
 
    if(tamanho_data  >  0) {

            
            memcpy(&fragmento.segmento,((void *) &pac.segmento)+p,tamanho_data);
            erro_mtu.MTU = -1;
            fragmento.tipo = 'F';
            fragmento.flagFragmentos = 0;
            fragmento.offset = p;
            fragmento.tamanho_data = tamanho_data;
            fragmento.identificacaoPacote = id;
            fragmento.no_dst = pac.no_src;
            fragmento.no_dst = pac.no_dst;
            fragmento.checksum_segmento = checksum_segmento;
            bufferRedeEnlace2.tamanho_data = tamanho_data;
            bufferRedeEnlace2.no_dst =tabelaRede[pac.no_dst][1];
            bufferRedeEnlace2.preenchido = 1;
            if(TESTE_SINC) printf("Fragmento %d foi para camada de enlace\n",fragmento.offset);
            memcpy(&bufferRedeEnlace2.pacote,&fragmento,sizeof(fragmento));
            sinc_rede = 1; 
            while(sinc_rede == 1) usleep(10);
            
      
    }
                
            
    

}

void fragmentacaoMTU2(pacote pac, int MTU) {
    
    int tamanho_data = pac.tamanho_data;
    int i = 0;
    int p = pac.offset - pac.tamanho_data;
    int padrao_divisao = MTU - (sizeof(quadro) - sizeof(pacote));
    if(TESTE_SINC) printf("padrao_divisao:%d\n",padrao_divisao);
    pacote fragmento;
    if(TESTE_SINC) printf("Tamanho_data:%d\n",tamanho_data);
    if(pac.offset == 0) p = 0;
    while(tamanho_data > padrao_divisao)
    { 
        //while(sinc_rede == 1) usleep(10); 
        memcpy(&fragmento.segmento,((void *) &pac.segmento)+p,padrao_divisao);
        erro_mtu.MTU = -1;
        fragmento.offset = p;
        p = p + padrao_divisao;
        fragmento.identificacaoPacote = pac.identificacaoPacote;
        fragmento.tipo = 'F';
        fragmento.flagFragmentos = 1;
        fragmento.tamanho_data = padrao_divisao;
        fragmento.checksum_segmento = pac.checksum_segmento;
        fragmento.no_dst = pac.no_src;
        fragmento.no_dst = pac.no_dst;
        bufferRedeEnlace2.tamanho_data = padrao_divisao;
        bufferRedeEnlace2.no_dst = tabelaRede[bufferEnlaceRede.pacote.no_dst][1];;
        bufferRedeEnlace2.preenchido = 2;
        if(TESTE_SINC) printf("Fragmento %d foi para camada de enlace\n",fragmento.offset);
        memcpy(&bufferRedeEnlace2.pacote,&fragmento,sizeof(fragmento));
        sinc_rede = 1;
        tamanho_data = tamanho_data - padrao_divisao;
        if(TESTE_SINC) printf("------------------------------------------------\n");
    }
 
 
    if(tamanho_data  >  0) {

            memcpy(&fragmento.segmento,((void *) &pac.segmento)+p,tamanho_data);
            erro_mtu.MTU = -1;
            fragmento.tipo = 'F';
            fragmento.flagFragmentos = 0;
            fragmento.offset = p;
            fragmento.tamanho_data = tamanho_data;
            fragmento.identificacaoPacote = pac.identificacaoPacote;
            fragmento.checksum_segmento = pac.checksum_segmento;
            fragmento.no_dst = pac.no_src;
            fragmento.no_dst = pac.no_dst;
            bufferRedeEnlace2.tamanho_data = tamanho_data;
            bufferRedeEnlace2.no_dst = tabelaRede[bufferEnlaceRede.pacote.no_dst][1];;
            bufferRedeEnlace2.preenchido = 2;
            if(TESTE_SINC) printf("Fragmento %d foi para camada de enlace\n",fragmento.offset);
            memcpy(&bufferRedeEnlace2.pacote,&fragmento,sizeof(fragmento));
            sinc_rede = 1;   
      
    }
                
}

void* recebePacote(void)
{
    
    pthread_mutex_lock(&sinc_recebe_rede_transp);
    //pthread_mutex_lock(&sinc_rede);

    while(enviaPacoteInicializado == 0)
        sleep(1);

    if(pthread_create(&(thread_rede_id[3]), NULL, &enviaTabelaParaVizinhos) != 0) {
        perror("pthread_create()");
        exit(1);
    }

    while(1) {

        // Aguarda a camada de enlace ser iniciada
        pthread_mutex_lock(&sinc_recebe_enlace_rede);
        while(bufferEnlaceRede.preenchido == 0) {
            if(TESTE_SINC) printf("Rede -> Não tem nada no buffer do enlace, aguardando...\n");
            sleep(1);
            pthread_mutex_unlock(&sinc_recebe_enlace_rede);
            usleep(10);
            pthread_mutex_lock(&sinc_recebe_enlace_rede);
        }

        if(TESTE_SINC) printf("Rede -> Recebi um pacote, posso trata-lo.\n");
        bufferEnlaceRede.preenchido = 0;

        if(bufferEnlaceRede.pacote.tipo == 'R') {
            if(TESTE_SINC) printf("Rede -> O pacote é do tipo R - Interpretar ele.\n");

            memcpy(&tabelaRedeRecebida, &bufferEnlaceRede.pacote, sizeof(tabelaRedeRecebida));
            tabelaRedeRecebida[0][0] = 0;

            int i, foi_modificado, origem;

            // Descobrir quem está me mandando a tabela
            for(i=0;i<qtdeNos; i++)
                if(tabelaRedeRecebida[i][2]==0)
                    origem = tabelaRedeRecebida[i][0];

            // Atualiza a minha tabela
            foi_modificado = 0;
            for(i=0; i<qtdeNos; i++) {
                if(tabelaRedeRecebida[i][2] + 1 < tabelaRede[i][2]) {
                    tabelaRede[i][2] = tabelaRedeRecebida[i][2] + 1;
                    tabelaRede[i][1] = origem;
                    foi_modificado = 1;
                }
            }

            // Se for atualizada, envio a nova tabela para os vizinhos
            if(foi_modificado) {
                if(TESTE_SINC) printf("Rede -> Minha tabela foi modificada.\n");
                if(TESTE_SINC) printTabelaRede();
                if(pthread_create(&(thread_rede_id[3]), NULL, &enviaTabelaParaVizinhos) != 0) {
                    perror("pthread_create()");
                    exit(1);
                }
            }
        }
        else {
            // O pacote é para mim?
            if(bufferEnlaceRede.pacote.no_dst == meuIndiceNo) {
                if(bufferEnlaceRede.pacote.tipo == 'F') {
                    printf("RECEBI FRAGMENTO VOU JUNTAR\n");
                    buffer_junta[controle_junta] = bufferEnlaceRede.pacote;
                    juntaFragmentos(buffer_junta,controle_junta+1);
                    if(flag_junta == 1) {
                        memcpy(&bufferRedeTransporte.segmento, &data_juncao, sizeof(data_juncao));
                        bufferRedeTransporte.preenchido = 1;
                        pthread_mutex_unlock(&sinc_recebe_rede_transp);
                        usleep(10);
                        pthread_mutex_lock(&sinc_recebe_rede_transp);

                        
                    }
                    controle_junta++;
                }
                else {
                    memcpy(&bufferRedeTransporte.segmento, &bufferEnlaceRede.pacote.segmento, sizeof(bufferRedeTransporte.segmento));

                    bufferRedeTransporte.preenchido = 1;
                    pthread_mutex_unlock(&sinc_recebe_rede_transp);
                    usleep(10);
                    pthread_mutex_lock(&sinc_recebe_rede_transp);
                }
            // O pacote não é pra mim
            } else {
                // Roteia o pacote
                bufferRedeEnlace2.no_dst = tabelaRede[bufferEnlaceRede.pacote.no_dst][1];
                memcpy(&bufferRedeEnlace2.pacote, &bufferEnlaceRede.pacote, sizeof(bufferRedeEnlace2.pacote));
                bufferRedeEnlace2.preenchido = 2;
                bufferRedeEnlace2.tamanho_data = bufferEnlaceRede.pacote.tamanho_data;
                
                // Aguarda enviaPacote2 enviar para o enlace o pacote roteado
                sinc_rede = 1;
                while(sinc_rede == 1) usleep(10);
                
                pthread_mutex_lock(&sinc_erro_mtu2);

                if(erro_mtu.MTU > 0) {
                    if(TESTE_SINC) printf("Rede -> ENTREI NA AREA DE FRAGMENTACAO ROTEAMENTO.\n");
                   
                    if(erro_mtu.pacote.tipo == 'F') {
                        if(TESTE_SINC) printf("Rede -> AREA FRAGMENTACAO DE FRAGMENTOS\n");  
                        fragmentacaoMTU2(erro_mtu.pacote, erro_mtu.MTU);
                    }
                    else
                        fragmentacaoMTU(erro_mtu.pacote, erro_mtu.MTU);
                   erro_mtu.MTU = 0;
               }
            }
        }
      pthread_mutex_unlock(&sinc_recebe_enlace_rede);
	usleep(10);
    }
}




void* enviaPacote(void)
{
    
    pthread_mutex_lock(&sinc_envia_enlace_rede);

    while(1) {
        // Aguarda a camada de transporte ser iniciada
        pthread_mutex_lock(&sinc_envia_rede_transp);
        while(bufferTransporteRede.preenchido == 0) {
            if(TESTE_SINC) printf("Rede -> Não tem nada no buffer de transporte, aguardando...\n");
            sleep(1);
            pthread_mutex_unlock(&sinc_envia_rede_transp);
	        usleep(10);
            pthread_mutex_lock(&sinc_envia_rede_transp);
        }

        bufferTransporteRede.preenchido = 0;
        bufferRedeEnlace.preenchido = 1;
        bufferRedeEnlace.no_dst = tabelaRede[bufferTransporteRede.no_dst][1];
        bufferRedeEnlace.pacote.tipo = 'X';
        bufferRedeEnlace.pacote.no_src = meuIndiceNo;
        bufferRedeEnlace.pacote.no_dst = bufferTransporteRede.no_dst;
        bufferRedeEnlace.tamanho_data = sizeof(bufferTransporteRede.segmento);
        bufferRedeEnlace.pacote.tamanho_data = sizeof(bufferTransporteRede.segmento);
        memcpy(&bufferRedeEnlace.pacote.segmento, &bufferTransporteRede.segmento, sizeof(bufferRedeEnlace.pacote.segmento));

        if(bufferRedeEnlace.no_dst != -1) { // Existe destinatário
		    
            // Libero enviaQuadro() para enviar o pacote
            if(TESTE_SINC) printf("Rede -> Buffer preenchido, liberando sincronia envia.\n");
    	    pthread_mutex_unlock(&sinc_envia_enlace_rede);
    	    usleep(10);
    	    pthread_mutex_lock(&sinc_erro_mtu);
    	    
            if(erro_mtu.MTU == 0) {
                if(TESTE_SINC) printf("Rede -> NAO PRECISO FRAGMENTAR.\n");
    	        pthread_mutex_lock(&sinc_envia_enlace_rede);
    	        if(TESTE_SINC) printf("Rede -> Fui liberado para enviar outro pacote, fechando sincronia envia.\n");
	        }
		    else {
                if(TESTE_SINC) printf("Rede -> ENTREI NA AREA DE FRAGMENTACAO\n");
                fragmentacaoMTU(erro_mtu.pacote, erro_mtu.MTU);
                erro_mtu.MTU = 0;
                pthread_mutex_lock(&sinc_envia_enlace_rede);
                if(TESTE_SINC) printf("Rede -> Fui liberado para enviar outro pacote, fechando sincronia envia.\n");
		    }        
        } 
        else {
            pthread_mutex_lock(&sinc_envia_enlace_rede);
            if(TESTE_SINC) printf("Rede -> Fui liberado para enviar outro pacote, fechando sincronia envia.\n");
        		     
        }
        pthread_mutex_unlock(&sinc_envia_rede_transp);
	   usleep(10);
    }
}

void* enviaPacote2(void)
{
    
    pthread_mutex_lock(&sinc_envia_enlace_rede2);
    enviaPacoteInicializado = 1;

    while(1) {
        // pthread_mutex_lock(&sinc_rede);

        while(sinc_rede != 1)
            usleep(10);

        // while(bufferRedeEnlace2.preenchido != 2) {
        //     if(TESTE_SINC) printf("Rede2 -> Não tem nada no buffer da rede, aguardando...\n");
        //     sleep(1);
        //     pthread_mutex_unlock(&sinc_rede);
        //     pthread_mutex_lock(&sinc_rede);
        // }

        bufferRedeEnlace2.preenchido = 1;
        
        if(TESTE_SINC) printf("Rede2 -> Buffer preenchido, liberando sincronia envia.\n");
        pthread_mutex_unlock(&sinc_envia_enlace_rede2);
        usleep(10);
        pthread_mutex_lock(&sinc_envia_enlace_rede2);
        if(TESTE_SINC) printf("Rede2 -> Fui liberado para enviar outro pacote, fechando sincronia envia.\n");

        sinc_rede = 0;
        // pthread_mutex_unlock(&sinc_rede);
    }
}

void recebeInfoEnlace(void)
{
    int i;

    // Zerar a tabela de rede
    for(i = 0; i < bufferTabelaRede.numero_nos; i++) {
        tabelaRede[i][0] = bufferTabelaRede.numero_no[i];
        tabelaRede[i][1] = -1;
        tabelaRede[i][2] = 9999;
        tabelaRede[i][3] = bufferTabelaRede.is_vizinho[i];
    }

    // Para enviar para mim, devo enviar para mim e o custo é 0
    tabelaRede[meuIndiceNo][0] = meuIndiceNo;
    tabelaRede[meuIndiceNo][1] = meuIndiceNo;
    tabelaRede[meuIndiceNo][2] = 0;
    tabelaRede[meuIndiceNo][3] = 0;
}

void printTabelaRede(void) {
    int i;
    for(i = 0; i < qtdeNos; i++)
        if(TESTE_SINC) printf("DST: %d, CAMINHO: %d, CUSTO: %d, IS_VIZINHO: %d\n", tabelaRede[i][0], tabelaRede[i][1], tabelaRede[i][2], tabelaRede[i][3]);
    
}

void* enviaTabelaParaVizinhos(void) {
    
    int i;

    memcpy(&bufferRedeEnlace2.pacote, &tabelaRede, sizeof(tabelaRede));

    for(i=0; i<qtdeNos; i++) {
        if(tabelaRede[i][3] == 1) {
            
            while(sinc_rede == 1)
                usleep(10);

            if(TESTE_SINC) printf("Rede -> Enviar atualização da tabela para %d.\n", tabelaRede[i][0]);
            bufferRedeEnlace2.no_dst = tabelaRede[i][0];
            bufferRedeEnlace2.preenchido = 2;
            bufferRedeEnlace2.pacote.tipo = 'R';
            bufferRedeEnlace2.tamanho_data = sizeof(tabelaRede);
            sinc_rede = 1;
            // pthread_mutex_unlock(&sinc_rede);
            // pthread_mutex_lock(&sinc_rede);
        }
    }
}
